package com.clinic.logic.appointment;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.clinic.accessor.db.AppointmentAccessor;
import com.clinic.accessor.db.PatientAccessor;
import com.clinic.entity.db.Appointment;
import com.clinic.entity.db.Patient;
import com.clinic.entity.gui.GuiAppointment;
import com.clinic.entity.gui.GuiPatient;
import com.clinic.entity.param.Index;
import com.clinic.entity.type.AppointmentStatus;
import com.clinic.util.CommonUtil;
import com.clinic.util.ConvertUtil;

@Component
public class AppointmentLogic {
	
	private static final String DEFAULT_TIME = "6:00";

	@Autowired
	private AppointmentAccessor appointmentAccessor;
	
	@Autowired
	private PatientAccessor patientAccessor;
	
	@Transactional
	public GuiAppointment getPatientAppointmentByPatient(GuiPatient guiPatient) {
		GuiAppointment guiAppointment = new GuiAppointment();
		guiAppointment.setPatient(guiPatient);
		guiAppointment.setCodeOrName(guiPatient.getName());
		guiAppointment.setDate(CommonUtil.getDateStringFormatByDate(CommonUtil.getTodayDate()));
		guiAppointment.setTime(DEFAULT_TIME);
		
		if(guiPatient.isAvailable()) {
			guiAppointment.setPatientStatus(AppointmentStatus.REGISTERED.toString());
		} else {
			guiAppointment.setPatientStatus(AppointmentStatus.UNREGISTERED.toString());
		}
		
		return guiAppointment;
	}
	
	@Transactional
	public GuiAppointment insertAppointment(GuiAppointment guiAppointment) {		
		Appointment appointment = new Appointment();
		long patientId = guiAppointment.getPatient().getUserId();
		Patient patient = patientAccessor.getPatientById(patientId);
		appointment.setPatient(patient);
		appointment.setDateAndTime(CommonUtil.getDateTimeByDateAndTime(guiAppointment.getDate(), guiAppointment.getTime()));
		
		Appointment result = appointmentAccessor.insertOrUpdate(appointment);
		return getGuiAppointmentByAppointment(result);
	}
	
	@Transactional
	public GuiAppointment updateAppointment(GuiAppointment guiAppointment) {
		Appointment appointment = appointmentAccessor.getAppointmentById(guiAppointment.getAppointmentId());
		long patientId = guiAppointment.getPatient().getUserId();
		Patient patient = patientAccessor.getPatientById(patientId);
		appointment.setPatient(patient);
		appointment.setDateAndTime(CommonUtil.getDateTimeByDateAndTime(guiAppointment.getDate(), guiAppointment.getTime()));
		
		Appointment result = appointmentAccessor.insertOrUpdate(appointment);
		return getGuiAppointmentByAppointment(result);
	}
	
	@Transactional
	public GuiAppointment getUpcomingPatientAppointment(long userId, Date date) {
		Appointment appointment = appointmentAccessor.getUpcomingPatientAppointment(userId, date);
		return getGuiAppointmentByAppointment(appointment);
	}
	
	@Transactional
	public List<GuiAppointment> getFutureOrOnDateAppointments(Date date, Index index) {
		List<GuiAppointment> result = new ArrayList<>();
		List<Appointment> appointmentList = appointmentAccessor.getFutureOrOnDateAppointments(date, index);
		for (Appointment appointment : appointmentList) {
			result.add(getGuiAppointmentByAppointment(appointment));
		}
		return result;
	}
	
	public List<GuiAppointment> getAppointmentListByPatientId(long patientId, Index index) {
		List<GuiAppointment> result = new ArrayList<>();
		List<Appointment> appointmentList = appointmentAccessor.getAppointmentsByPatientId(patientId, index);
		for (Appointment appointment : appointmentList) {
			result.add(getGuiAppointmentByAppointment(appointment));
		}
		return result;
	}
	
	public Map<Integer, Map<String, GuiAppointment>> getDayAppointmentMapByAppointmentList(List<GuiAppointment> guiAppointmentList) {
		Map<Integer, Map<String, GuiAppointment>> result = new HashMap<>();
		
		// create default map
		int[] hours = {6, 7, 8, 9};
		for (int hour : hours) {
			Map<String, GuiAppointment> minMap = new LinkedHashMap<>(); // LinkedHashMap keeps insert order
			
			minMap.put("00", null);
			minMap.put("15", null);
			minMap.put("30", null);
			minMap.put("45", null);
			
			result.put(hour, minMap);
		}
		
		// fill appointments
		for (GuiAppointment guiAppointment : guiAppointmentList) {
			String aTime = guiAppointment.getTime(); // ex - 6:00 (Hour:Minutes)
			String[] timeParts = aTime.split(":");	// ["6", "00"]
			int hour = Integer.parseInt(timeParts[0]);
			String min = timeParts[1];
			result.get(hour).put(min, guiAppointment);		
		}
		
		return result;
	}

	public Map<Integer, List<GuiAppointment>> getMonthAppointmentMapByAppointmentList(List<GuiAppointment> guiAppointmentList, Date date) {
		Map<Integer, List<GuiAppointment>> result = new HashMap<>();
		
		Date sDate = CommonUtil.getStartDateOfMonthByDate(date);
		Date eDate = CommonUtil.getEndDateOfMonthByDate(date);
		
		List<Date> dateList = CommonUtil.getListOfDaysBetweenTwoDates(sDate, eDate);
		
		for (Date oDate : dateList) {
			int day = Integer.valueOf(CommonUtil.getDayStringFormatByDate(oDate));
			result.put(day, new ArrayList<GuiAppointment>());
		}
		
		for (GuiAppointment guiAppointment : guiAppointmentList) {
			String aDate = guiAppointment.getDate();
			String[] dateParts = aDate.split("-");
			int day = Integer.valueOf(dateParts[2]);
			result.get(day).add(guiAppointment);
		}
		
		return result;
	}
	
	@Transactional
	public GuiAppointment getAppointmentDetailsById(long id) {
		Appointment appointment = appointmentAccessor.getAppointmentById(id);
		return getGuiAppointmentByAppointment(appointment);
	}
	
	@Transactional
	public GuiAppointment getPatientAppointmentByDate(long userId, Date date) {
		Date startDate = date;
		Date endDate = CommonUtil.addDays(startDate, 1);
		Appointment appointment = appointmentAccessor.getPatientAppointmentByDate(userId, startDate, endDate);
		return getGuiAppointmentByAppointment(appointment);
	}
	
	@Transactional
	public List<GuiAppointment> getDayAppointmentListByDate(Date date) {
		Date startDate = date;
		Date endDate = CommonUtil.addDays(startDate, 1);
		List<GuiAppointment> result = new ArrayList<>();
		List<Appointment> appointmentList = appointmentAccessor.getAppointmentsByDate(startDate, endDate);
		for (Appointment appointment : appointmentList) {
			GuiAppointment guiAppointment = getGuiAppointmentByAppointment(appointment);
			result.add(guiAppointment);
		}
		return result;
	}
	
	@Transactional
	public List<GuiAppointment> getMonthAppointmentListByDate(Date date) {
		Date startDayOfMonth = CommonUtil.getStartDateOfMonthByDate(date); 
        Date lastDayOfMonth = CommonUtil.getEndDateOfMonthByDate(date); 
        List<GuiAppointment> result = new ArrayList<>();
		List<Appointment> appointmentList = appointmentAccessor.getAppointmentsByDate(startDayOfMonth, lastDayOfMonth);
		for (Appointment appointment : appointmentList) {
			GuiAppointment guiAppointment = getGuiAppointmentByAppointment(appointment);
			result.add(guiAppointment);
		}
		return result;
	}
	
	@Transactional
	public List<GuiAppointment> getAppointmentListByPatient(long patientId, Index index) {
		List<GuiAppointment> result = new ArrayList<>();
		List<Appointment> appointmentList = appointmentAccessor.getAppointmentsByPatientId(patientId, index);
		for (Appointment appointment : appointmentList) {
			GuiAppointment guiAppointment = getGuiAppointmentByAppointment(appointment);
			result.add(guiAppointment);
		}
		return result;
	}
	
	@Transactional
	public void deleteAppointmentDetailsById(long id) {
		Appointment appointment = appointmentAccessor.getAppointmentById(id);
		if (appointment != null) {
			appointment.setPatient(null);
			appointmentAccessor.delete(appointment);
		}
	}
	
	@Transactional
	public boolean isAvailableAppointmentTime(String strAptDate, String strAptTime) {
		Date aptDate = CommonUtil.getDateByStringFormat(strAptDate);
		List<GuiAppointment> dayAppointmentList = getDayAppointmentListByDate(aptDate);
		for (GuiAppointment oGuiAppointment : dayAppointmentList) {
			if (strAptDate.equals(oGuiAppointment.getDate())
					&& strAptTime.equals(oGuiAppointment.getTime())
					) {
				return false;
			}
		}
		return true;
	}
	
	public String getNextDateString(Date date) {
		Date nextDate = CommonUtil.addDays(date, 1);
		return CommonUtil.getDateStringFormatByDate(nextDate);
	}

	public String getPrevDateString(Date date) {
		Date nextDate = CommonUtil.addDays(date, -1);
		return CommonUtil.getDateStringFormatByDate(nextDate);
	}
	
	private GuiAppointment getGuiAppointmentByAppointment(Appointment appointment) {
		GuiAppointment result = ConvertUtil.getGuiAppointmentByAppointment(appointment);
		if (result.isAvailable()) {
			GuiPatient guiPatient = ConvertUtil.getGuiPatientByPatient(appointment.getPatient());
			result.setPatient(guiPatient);
			if(guiPatient.isAvailable()) {
				result.setPatientStatus(AppointmentStatus.REGISTERED.toString());
			} else {
				result.setPatientStatus(AppointmentStatus.UNREGISTERED.toString());
			}
		}
		return result;
	}

}
